/*
 * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.baomidou.mybatisplus.advance.query;

import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.advance.injector.FuntionTools;
import com.baomidou.mybatisplus.advance.query.anno.SafeField;
import com.baomidou.mybatisplus.advance.query.enums.DirectionEnum;
import com.baomidou.mybatisplus.advance.query.enums.OperatorEnum;
import com.baomidou.mybatisplus.advance.query.enums.RelationEnum;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaJoinQueryWrapper;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.apache.ibatis.reflection.property.PropertyNamer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;

import static com.baomidou.mybatisplus.advance.query.enums.OperatorEnum.IS_NULL;
import static com.baomidou.mybatisplus.advance.query.enums.OperatorEnum.NOT_NULL;

/**
 * 前端高级查询实体
 *
 * @author wanglei
 * @since 2022-03-14
 */
@Data
@Builder
@AllArgsConstructor
@ApiModel("前端高级查询实体")
@Schema(title = "前端高级查询实体")
public class QueryFilter<T> {


    @ApiModelProperty("分页信息")
    @Schema(title = "分页信息")
    private DefaultPager<T> pagerInfo;

    @ApiModelProperty(
            name = "sorter",
            notes = "排序字段"
    )
    @Schema(title = "排序字段")
    private List<FieldSort> sorter = new ArrayList<>();
    @ApiModelProperty(
            name = "params",
            notes = "扩展参数"
    )
    @Schema(title = "扩展参数")
    private Map<String, Object> params = new HashMap<>();
    @ApiModelProperty(
            name = "querys",
            notes = "查询条件集合"
    )
    @Schema(title = "查询条件集合")
    private List<QueryField> querys = new ArrayList<>();

    @ApiModelProperty(
            name = "groupRelation",
            notes = "查询条件分组的关系",
            example = "AND"
    )
    @Schema(title = "查询条件分组的关系", example = "AND")
    private String groupRelation;

    @ApiModelProperty(
            name = "fields",
            notes = "需要返回前端的字段-不传则返回全部",
            example = "[\"name\",\"age\"]"
    )
    @Schema(title = "需要返回前端的字段", example = "AND")
    private String[] fields;

    /**
     * 安全字段，就算前端输入了这些字段后端也不拼接相关条件
     */
    @JsonIgnore
    @JSONField(serialize = false)
    private Set<String> safeFieldsSet = new HashSet<>();

    public QueryFilter() {
        this.groupRelation = RelationEnum.AND.name();
    }


    /**
     * 根据排序规则分组
     *
     * @return 分组后的排序字段
     */
    public Map<DirectionEnum, List<FieldSort>> groupFieldSort() {
        if (this.sorter == null) {
            return new HashMap<>();
        }
        return this.sorter.stream().collect(Collectors.groupingBy(FieldSort::getDirection));
    }

    /**
     * 类似bean seacher的高级查询语法支持
     *
     * @param currentModelClass
     * @param <Z>
     * @return
     */
    public static <Z> LambdaJoinQueryWrapper<Z> reqParam2Wrapper(Class<Z> currentModelClass, Map<String, Object> paramMap) {
        // 获取所有字段
        List<String> fieldNames = getAllField(currentModelClass).stream().map(Field::getName).collect(Collectors.toList());
        QueryFilter<Z> queryFilter = new QueryFilter<>();
        for (String fieldName : fieldNames) {
            //有值并且不为空的才处理
            if (paramMap.containsKey(fieldName) && !StringUtils.isEmpty(paramMap.get(fieldName) + "")) {
                QueryField queryField = new QueryField();
                //指定了运算符则使用指定的运算符，没有指定则使用=
                if (paramMap.containsKey(fieldName + "-op")) {
                    queryField.setOperator(paramMap.get(fieldName + "-op") + "");
                } else {
                    queryField.setOperator("=");
                }
                queryField.setProperty(fieldName);
                queryField.setValue(paramMap.get(fieldName));
                queryFilter.getQuerys().add(queryField);
            }
        }
        //处理is null和 not_null
        for (Map.Entry<String, Object> paramEntry : paramMap.entrySet()) {
            if (IS_NULL.getOperator().equals(paramEntry.getValue()) || NOT_NULL.getOperator().equals(paramEntry.getValue())) {
                QueryField queryField = new QueryField();
                queryField.setOperator(paramEntry.getValue() + "");
                queryField.setProperty(paramEntry.getKey().replace("-op", ""));
                queryFilter.getQuerys().add(queryField);
            }
        }
        return queryFilter.asWrapper(currentModelClass);
    }

    /**
     * 转换高级查询API为 QueryWrapper
     *
     * @param currentModelClass 实体类
     * @return QueryWrapper
     */
    public LambdaJoinQueryWrapper<T> asWrapper(Class<T> currentModelClass, SFunction<T, ?>... safeFields) {
        //处理安全字段
        if (safeFields != null) {
            for (SFunction<T, ?> safeField : safeFields) {
                LambdaMeta meta = LambdaUtils.extract(safeField);
                String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());
                safeFieldsSet.add(fieldName);
            }
        }
        List<Field> safeFieldList = getAnnotationField(currentModelClass, SafeField.class);
        if (!safeFieldList.isEmpty()) {
            safeFieldsSet.addAll(safeFieldList.stream().map(Field::getName).collect(Collectors.toList()));
        }
        LambdaJoinQueryWrapper<T> queryWrapper = new LambdaJoinQueryWrapper<>(currentModelClass);
        // 过滤掉安全字段
        List<QueryField> newListFields = this.querys.stream().filter(queryField -> !safeFieldsSet.contains(queryField.getProperty()))
                .filter(queryField -> {
                    String value = queryField.getValue() == null ? null : queryField.getValue().toString();
                    return value != null && !"".equals(value) && !"[]".equals(value);
                })
                .collect(Collectors.toList());
        if (!newListFields.isEmpty()) {
            queryWrapper.and((x) -> {
                newListFields.forEach((l) -> {
                    this.convertQueryField((LambdaJoinQueryWrapper<T>) x, l, currentModelClass);
                });
            });
        }
        Map<DirectionEnum, List<FieldSort>> groupFieldSort = this.groupFieldSort();
        // 处理排序
        groupFieldSort.forEach((d, l) -> {
            if (DirectionEnum.DESC.equals(d)) {
                queryWrapper.orderByDesc(this.convertSortFieldList(l, currentModelClass));
            } else {
                queryWrapper.orderByAsc(this.convertSortFieldList(l, currentModelClass));
            }

        });
        return queryWrapper;
    }


    /**
     * 转换排序字段
     *
     * @param list              排序条件集合
     * @param currentModelClass 实体类
     * @return 排序字段名集合
     */
    private List<SFunction<T, ?>> convertSortFieldList(List<FieldSort> list, Class<T> currentModelClass) {
        if (list == null) {
            return null;
        } else {
            List<SFunction<T, ?>> result = new ArrayList<>();
            for (FieldSort fieldSort : list) {
                result.add(FuntionTools.getSFunction(currentModelClass, fieldSort.getProperty()));
            }
            return result;
        }
    }

    /**
     * 获取数据库字段
     *
     * @param fieldName         Java字段名
     * @param currentModelClass 类
     * @return 数据库字段名
     */
    @JSONField(serialize = false)
    public SFunction getField(String fieldName, Class<T> currentModelClass) {
        return FuntionTools.getSFunction(currentModelClass, fieldName);
    }

    /**
     * 将query filed 转换添加到  queryWrapper 中
     *
     * @param queryWrapper      QueryWrapper
     * @param queryField        字段
     * @param currentModelClass 实体类
     */
    private void convertQueryField(LambdaJoinQueryWrapper<T> queryWrapper, QueryField queryField, Class<T> currentModelClass) {
        SFunction field = null;
        if (queryField.getTarget() != null) {
            try {
                Class<T> targetClass = (Class<T>) Class.forName(queryField.getTarget());
                queryWrapper.innerJoin(targetClass);
                field = getField(queryField.getProperty(), targetClass);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } else {
            field = getField(queryField.getProperty(), currentModelClass);
        }
        String operator = queryField.getOperator();
        //如果是空或者null 字符串则不加此过滤条件
        if (queryField.getValue() == null || "".equals(queryField.getValue()) || "null".equals(queryField.getValue() + "")) {
            return;
        }
        switch (OperatorEnum.getByOperator(operator)) {
            case EQ:
                queryWrapper.eq(field, queryField.getValue());
                break;
            case LT:
                queryWrapper.lt(field, queryField.getValue());
                break;
            case GT:
                queryWrapper.gt(field, queryField.getValue());
                break;
            case LE:
                queryWrapper.le(field, queryField.getValue());
                break;
            case GE:
                queryWrapper.ge(field, queryField.getValue());
                break;
            case NE:
                queryWrapper.ne(field, queryField.getValue());
                break;
            case LIKE:
                queryWrapper.like(field, queryField.getValue());
                break;
            case LIKE_LEFT:
                queryWrapper.likeLeft(field, queryField.getValue());
                break;
            case LIKE_RIGHT:
                queryWrapper.likeRight(field, queryField.getValue());
                break;
            case NOT_LIKE:
                queryWrapper.notLike(field, queryField.getValue());
                break;
            case IS_NULL:
                queryWrapper.isNull(field);
                break;
            case NOT_NULL:
                queryWrapper.isNotNull(field);
                break;
            case IN:
                Object[] values = this.convert2ObjectArray(queryField.getValue());
                if (values != null && values.length > 0) {
                    queryWrapper.in(field, values);
                }
                break;
            case BETWEEN://前端经常用的是 时间过滤，比如查询 2020-01-01 到2020-01-02 如果用between会是 >   2020-01-01 and 2020-01-02<
                Object[] objs = this.convert2ObjectArray(queryField.getValue());
                if (objs != null && objs.length > 0) {
                    Assert.isTrue(objs.length == 2, String.format("The length of between must be 2 ：%s", objs));
                    queryWrapper.between(field, objs[0], objs[1]);
                }
                break;
            case NOT_BETWEEN://前端经常用的是 时间过滤，比如查询 2020-01-01 到2020-01-02 如果用between会是 >   2020-01-01 and 2020-01-02<
                Object[] objsNotBetween = this.convert2ObjectArray(queryField.getValue());
                if (objsNotBetween != null && objsNotBetween.length > 0) {
                    Assert.isTrue(objsNotBetween.length == 2, String.format("The length of between must be 2 ：%s", objsNotBetween));
                    queryWrapper.notBetween(field, objsNotBetween[0], objsNotBetween[1]);
                }
            case NOT_IN:
                Object[] notInValues = this.convert2ObjectArray(queryField.getValue());
                if (notInValues != null && notInValues.length > 0) {
                    queryWrapper.in(field, notInValues);
                }
                break;
        }

    }

    /**
     * 转换对象为数组
     *
     * @param obj value对象
     * @return 数组
     */
    private Object[] convert2ObjectArray(Object obj) {
        if (obj == null) {
            return new Object[]{"''"};
        } else if (!(obj instanceof String)) {
            if (obj instanceof List) {
                List<Object> objList = (List) obj;
                return objList.toArray();
            } else if (obj instanceof Object[]) {
                return (Object[]) obj;
            } else {
                return obj instanceof Object ? new Object[]{obj} : null;
            }
        } else {
            String str = obj.toString();
            String[] split = str.split(",");

            for (int i = 0; i < split.length; ++i) {
                split[i] = this.handleQuotation(split[i]);
            }

            return split;
        }
    }


    /**
     * 处理特殊符号
     *
     * @param str 字符串
     * @return 处理后的字符串
     */
    private String handleQuotation(String str) {
        String ResultString = str;
        try {
            Pattern regex = Pattern.compile("^'(.*)'$");
            Matcher regexMatcher = regex.matcher(str);
            if (regexMatcher.find()) {
                ResultString = regexMatcher.group(1);
            }
        } catch (PatternSyntaxException var5) {
        }
        return ResultString;
    }


    /**
     * 根据一个class和注解获取字段集合
     *
     * @param clazz           class
     * @param annotationClass 注解
     * @return 字段集合
     */
    private static <T extends Annotation> List<Field> getAnnotationField(Class<?> clazz, Class<T> annotationClass) {
        List<Field> result = new ArrayList<>();
        List<Field> fields = getAllField(clazz);
        for (Field field : fields) {
            if (field.getAnnotation(annotationClass) != null) {
                result.add(field);
            }
        }
        return result;
    }

    /**
     * 获取一个class的所有的字段
     *
     * @param clazz class
     * @return 所有字段
     */
    private static List<Field> getAllField(Class clazz) {
        Field[] fields = null;
        List<Field> result = new ArrayList<Field>();
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                fields = clazz.getDeclaredFields();
                result.addAll(Arrays.asList(fields));
            } catch (Exception e) {
                // 这里甚么都不要做！并且这里的异常必须这样写，不能抛出去。
                // 如果这里的异常打印或者往外抛，则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了
            }
        }
        return result;
    }

}
